#include <glib.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "parser.h"
#include "types/Regex.h"
#include "types/Vector.h"
#include "types/Pair.h"

enum TEXT2XML_TOKEN_TYPE {
    T_RE,
    T_STR,
    T_END
};

static unsigned long result     = 0;
static Regex*        varname_re = NULL;

inline void set_error(Parser* parser, const char* ctx);

/********************************************************************
 * Public.
 ********************************************************************/
float parser_get_progress(Parser* parser)
{
  return (float)parser->lexer->current_char / (float)parser->lexer->input_length;
}

void parser_stop(Parser* parser)
{
  parser->status = TEXT2XML_STATUS_STOPPED;
  set_error(parser, "Stopped");
}

const char* parser_error(Parser* parser)
{
  return parser->error;
}

/********************************************************************
 * Private.
 ********************************************************************/
void parser_debug(Parser* parser, int level, const char* str)
{
  if (level < 5)
    return;
  printf(str);
}

inline const char* get_current_line_start(Lexer* lexer)
{
  int         pos = lexer->current_char;
  const char* str = lexer->input + pos;
  while (str >= lexer->input && *str != '\n')
    str--;
  return str + 1;
}

inline void set_error(Parser* parser, const char* ctx)
{
  int         line  = parser->lexer->current_line;
  const char* start = get_current_line_start(parser->lexer);
  const char* end   = strchr(start, '\n');
  int         len   = end - start;
  char        str[101];
  if (end == NULL) {
    end = "";
    len = 0;
  }
  if (len > 100)
    len = 100;
  strncpy(str, start, len);
  *(str + len) = 0;
  snprintf(parser->error, 100, "In line %i (%s):\n'%s'", line, ctx, str);
  parser->status = TEXT2XML_STATUS_FAILED;
}

inline const char* replace_vars(Regex* regex, const char* src, void* data)
{
  Parser* parser = (Parser*)data;
  int     varno  = atol(src + 1);
  Vector* match  = parser->match_stack[parser->stack_depth];
  return (const char*)vector_get(match, varno);
}

inline short int xml_cb(Parser* parser, int type, const char* name)
{
  char* newname = NULL;
  if (name == NULL) {
    set_error(parser, "NULL node name");
    return FALSE;
  }
  if (type != T_DATA && type != T_CLOSE)
    regex_sub(varname_re, name, &newname, replace_vars, parser);
  else
    newname = strdup(name);
  if (newname == NULL) {
    set_error(parser, "NULL node name after substitution");
    return FALSE;
  }
  if (parser->xml_queue2 == NULL)
    parser->callback(parser, type, newname, parser->callback_data);
  else {
    GList* item = parser->xml_queue2;
    parser->callback(parser, T_OPEN, newname, parser->callback_data);
    while (item) {
      const char* xml_path = (const char*)item->data;
      parser->callback(parser, T_SINGLE, xml_path, parser->callback_data);
      item = item->next;
    }
    g_list_free(parser->xml_queue2);
    parser->xml_queue2 = NULL;
    if (type == T_SINGLE)
      parser->callback(parser, T_CLOSE, newname, parser->callback_data);
  }
  free(newname);
  return TRUE;
}

inline short int xml_cb_multi_real(Parser*     parser,
                                   const char* name, 
                                   const char* data,
                                   short int   append)
{
  if (!xml_cb(parser, T_OPEN,  name))
    return FALSE;
  if (!xml_cb(parser, append ? T_DATA_APPEND : T_DATA,  data))
    return FALSE;
  if (!xml_cb(parser, T_CLOSE, name))
    return FALSE;
  return TRUE;
}

inline short int xml_cb_multi(Parser*     parser,
                              const char* name, 
                              const char* data)
{
  xml_cb_multi_real(parser, name, data, TRUE);
  return TRUE;
}

inline short int xml_cb_multi_processdata(Parser*     parser,
                                          const char* name, 
                                          const char* data)
{
  char* newdata = NULL;
  short int res = 0;
  regex_sub(varname_re, data, &newdata, replace_vars, parser);
  res = xml_cb_multi_real(parser, name, newdata, FALSE);
  free(newdata);
  return res;
}

inline void xml_push(Parser* parser, const char* item)
{
  parser->xml_stack = g_list_append(parser->xml_stack, (void*)item);
}

inline void xml_pop(Parser* parser)
{
  GList*      item = NULL;
  const char* name = NULL;
  while (parser->xml_stack) {
    item              = g_list_last(parser->xml_stack);
    name              = (const char*)item->data;
    parser->xml_stack = g_list_delete_link(parser->xml_stack, item);
    if (!name)
      break;
    xml_cb(parser, T_CLOSE, name);
  }
}

inline void parser_enqueue(Parser*     parser,
                           Regex*      trigger,
                           const char* xml)
{
  void** pair = (void**)g_malloc0(2 * sizeof(void*));
  pair[0] = (void*)trigger;
  pair[1] = (void*)xml;
  parser->xml_queue = g_list_append(parser->xml_queue, pair);
}

/**
 * Moves all queued XML paths that match the given trigger into
 * queue 2. Moves all XML paths if trigger is NULL.
 */
inline short int parser_flush_queue(Parser* parser, Regex* trigger)
{
  GList* item = parser->xml_queue;
  //printf("Flushing called...\n");
  while (item) {
    void** pair = item->data;
    if (pair[0] != trigger && trigger != NULL) {
      item = item->next;
      continue;
    }
    if (pair[1] != NULL)
      parser->xml_queue2 = g_list_append(parser->xml_queue2, pair[1]);
    //printf("Flushing found match: %s\n", (const char*)pair[1]);
    g_free(pair);
    item = item->next;
    parser->xml_queue = g_list_remove(parser->xml_queue, pair);
  }
  return TRUE;
}

inline short int parser_match(Parser* parser, GList* tokens)
{
  Vector* match = NULL;
  GList*  item  = tokens;

  if (parser->match_stack[parser->stack_depth] != NULL)
    vector_free(parser->match_stack[parser->stack_depth]);
  match = lexer_next_if_re_list(parser->lexer, tokens);
  parser->match_stack[parser->stack_depth] = match;
  if (match == NULL)
    return FALSE;

  // Trigger all XML actions that are triggered by the regular expressions 
  // that just matched.
  while (item) {
    Regex* re = (Regex*)item->data;
    parser_flush_queue(parser, re);
    item = item->next;
  }
  return TRUE;
}
inline Regex* re_add(Parser* parser, const char* varname, Regex* regex)
{
  Pair* pair = (Pair*)g_malloc0(sizeof(Pair));
  pair->one  = (void*)varname;
  pair->two  = (void*)regex;
  parser->re_list = g_list_append(parser->re_list, pair);
  if (g_list_find(parser->re_garbage, regex) == NULL)
    parser->re_garbage = g_list_append(parser->re_garbage, regex);
  return regex;
}

inline Regex* re_get(Parser* parser, const char* varname)
{
  GList* item = parser->re_list;
  while (item) {
    Pair*       pair = (Pair*)item->data;
    const char* key  = (const char*)pair->one;
    if (strcmp(key, varname) == 0)
      return (Regex*)pair->two;
    item = item->next;
  }
  g_error("Attempt to access invalid RE '%s'\n", varname);
  return NULL;
}

inline void re_garbage_free(Parser* parser)
{
  GList* item = parser->re_garbage;
  while (item) {
    regex_free((Regex*)item->data);
    item = item->next;
  }
  g_list_free(parser->re_garbage);
}

GList* make_token_list(Parser* parser, int type, ...)
{
  va_list     arglist;
  GList*      list = NULL;
  Regex*      re   = NULL;
  const char* str  = NULL;

  va_start (arglist, type);
  do {
    if (type == T_END)
      break;
    if (type == T_RE)
      re = va_arg(arglist, Regex*);
    else if (type == T_STR) {
      str = va_arg(arglist, const char*);
      re  = regex_new(str, 0);
      re_add(parser, str, re);
    }
    else
      g_error("Invalid token type");
    list = g_list_append(list, re);
    type = va_arg(arglist, int);
  } while (1);
  va_end (arglist);

  return list;
}

void free_token_list(GList* list)
{
  g_list_free(list);
}

void glist_print(GList* list)
{
  printf("List: ");
  while (list) {
    printf("%s/", (const char*)list->data);
    list = list->next;
  }
  printf("\n");
}

// Imports parser_init_gen(), parser_free_gen(), parser_run_gen().
#include "generated.c"

Parser* parser_new(void) {
  Parser* parser      = (Parser*)g_malloc0(sizeof(Parser));
  parser->lexer       = lexer_new();
  parser->xml_stack   = NULL;
  parser->stack_depth = 0;
  parser->status      = TEXT2XML_STATUS_IDLE;
  parser->xml_queue   = NULL;
  parser->xml_queue2  = NULL;
  parser->re_list     = NULL;
  parser->re_garbage  = NULL;
  *(parser->error)    = 0;
  varname_re          = regex_new("\\$[0-9][0-9]*", 0);

  parser_init_gen(parser);

  return parser;
}

void parser_free(Parser* parser) {
  parser_free_gen(parser);

  lexer_free(parser->lexer);
  g_list_free(parser->xml_stack);
  parser_flush_queue(parser, NULL);
  g_list_free(parser->xml_queue2);
  re_garbage_free(parser);
  for (int i = 0; i < TEXT2XML_PARSER_MAX_STACK; i++)
    if (parser->match_stack[i] != NULL)
      vector_free(parser->match_stack[i]);
  g_free(parser);
  regex_free(varname_re);
}

int parser_run(Parser*            parser,
               const char*        string,
               ParserCallbackFunc callback,
               void*              callback_data) {
  int result = 0;
  parser->xml_stack     = NULL;
  parser->callback      = callback;
  parser->callback_data = callback_data;
  parser->status        = TEXT2XML_STATUS_RUNNING;
  lexer_set_input(parser->lexer, string);

  parser_run_gen(parser);

  do {
    result = match_input(parser);
    switch (parser->status) {
      case TEXT2XML_STATUS_LEVEL_OUT:
        set_error(parser, "Attempt to leave global scope");
        return 1;
        break;

      case TEXT2XML_STATUS_STOPPED:
        return 2;
        break;
        
      case TEXT2XML_STATUS_FAILED:
        return 3;
        break;

      case TEXT2XML_STATUS_RUNNING:
        break;

      default:
        set_error(parser, "Unknown error code");
        return 4;
        break;
    }
    if (result == 0) {
      set_error(parser, "No match in global context");
      return 5;
      break;
    }
  } while (!lexer_is_done(parser->lexer));
  parser->status = TEXT2XML_STATUS_IDLE;
  return 0;
}
